from __future__ import annotations
from typing import NamedTuple, Optional, TextIO
from configparser import ConfigParser
from pathlib import Path
import csv

Internal_Analysis_Setting = NamedTuple('Internal_Analysis_Setting', [('measurements_name', list[str]), ('tags', set[str]), ('must_has_tag', bool)])
External_Analysis_Setting = NamedTuple('External_Analysis_Setting', [('measurements_name', list[str]), ('tags0', set[str]), ('tags1', set[str]), ('must_has_tag', bool)])

PATH = Path(__file__).resolve().parent
BOOL_STR_MAP = {
    'True': True,
    'False': False,
    'true': True,
    'false': False,
    '1': True,
    '0': False
}

LEGAL_INTERNAL_MEASUREMENT_NAME = {'h_confidence', 'support'}
LEGAL_EXTERNAL_MEASUREMENT_NAME = {'support', 'confidence', 'lift', 'phi', 'chi2', 'pchi2'}
LEGAL_DATA_FORM_NAME = {'RDB-like', 'excel-like', 'excel-like-incell'}

def remove_utf8_bom(text: str) -> str:
    if text[0] == '\ufeff':
        return text[1: ]
    else:
        return text

class Setting:
    def __init__(self):
        with open(PATH.joinpath('../setting.ini'), 'r', encoding='utf-8') as f:
            setting_str = remove_utf8_bom(f.read())
        config = ConfigParser()
        config.read_string(setting_str)

        self.coding = config['basic']['coding']
        self.has_head = config.getboolean('basic', 'has_head')

        self.data_form = config['basic']['data_form']
        if self.data_form not in LEGAL_DATA_FORM_NAME:
            raise ValueError(f'data_form 仅支持\'RDB-like\'、\'excel-like\'、\'excel-like-incell\'三种, 当前设置为{self.data_form}, 请重新设置')

        # setting support and threshold
        threshold = config.get('basic', 'threshold', fallback=None)
        support = config.get('basic', 'support', fallback=None)
        if threshold is None:
            self.threshold = None
        else:
            self.threshold = int(threshold)
        if support is None:
            self.support = None
        else:
            self.support = float(support)
        if self.threshold is None:
            if self.support is None:
                raise ValueError('未设置threshold或support.')
            if (self.support < 0) or (self.support > 1):
                raise ValueError(f'support 必须在0-1之间, 当前设置为{self.support}.')
        
        # setting output dir and name_map file
        self.result_dir = Path(config['basic']['result'])
        self.name_map_file = Path(config['basic']['name_map_file'])

        # setting data_shape and tags
        self.all_data_index_text = config['data_shape']['all']
        if self.data_form == 'RDB-like':
            self.data_id_index = config.getint('data_shape', 'id') - 1
        self.tags_index_text: dict[str, str] = {}
        for tag_name, text in config['data_shape'].items():
            if tag_name not in ['all', 'id'] :
                self.tags_index_text[tag_name] = text

        # setting measurements
        self.internal_analysis_settings: list[Internal_Analysis_Setting] = []
        self.external_analysis_settings: list[External_Analysis_Setting] = []

        for _, text in config['internal_measurement'].items():
            self.internal_analysis_settings.append(self.internal_analysis_setting_parser(text))

        for _, text in config['external_measurement'].items():
            self.external_analysis_settings.append(self.external_analysis_setting_parser(text))

    def setting_file_path(self, data_file_path: Path) -> None:
        parent = data_file_path.parent
        self.absolute_result_dir = parent.joinpath(self.result_dir)
        self.absolute_name_map_file = parent.joinpath(self.name_map_file)
        if self.absolute_result_dir.exists():
            raise Exception(f'文件目录中已存在{self.result_dir}, 为防止数据误覆盖, 请重命名{self.result_dir}文件夹, 或在setting中修改basic.result的值')

    def setting_data_width(self, data_width: int) -> None:
        self.all_data_index = self.data_shape_parser(self.all_data_index_text, data_width)
        if self.tags_index_text == {}:
            self.index_tags_map: Optional[dict[int, set[str]]] = None
        else:
            self.index_tags_map = {}
            for tag, text in self.tags_index_text.items():
                indexs = self.data_shape_parser(text, data_width)
                for index in indexs:
                    self.index_tags_map.setdefault(index, set()).add(tag)

    def data_shape_parser(self, text: str, data_width: int) -> set[int]:
        output: set[int] = set()
        text = text.strip()
        sections = text.split(',')
        for section in sections:
            section = section.strip()
            if '-' in section:
                begin, end = section.split('-')
                if begin == '':
                    _begin = 0
                else:
                    _begin = int(begin) - 1
                if end == '':
                    _end = data_width - 1
                else:
                    _end = int(end) - 1
                for index in range(_begin, _end + 1):
                    output.add(index)
            else:
                output.add(int(section) - 1)
        return output

    def internal_analysis_setting_parser(self, text: str) -> Internal_Analysis_Setting:
        measurements_name_text, tags_text, must_has_tag_text = text.strip().split('-')
        measurements_name: list[str] = []
        tags: set[str] = set()
        for measurement_name_text in measurements_name_text.split(','):
            measurement_name_text = measurement_name_text.strip()
            if measurement_name_text in LEGAL_INTERNAL_MEASUREMENT_NAME:
                measurements_name.append(measurement_name_text)
            else:
                raise ValueError(f'Invalid measurement name {measurement_name_text}')
        for tag_text in tags_text.split(','):
            tags.add(tag_text.strip())
        must_has_tag = BOOL_STR_MAP[must_has_tag_text.strip()]
        return Internal_Analysis_Setting(measurements_name, tags, must_has_tag)

    def external_analysis_setting_parser(self, text: str) -> External_Analysis_Setting:
        measurements_name_text, tags0_text, tags1_text, must_has_tag_text = text.strip().split('-')
        measurements_name: list[str] = []
        tags0: set[str] = set()
        tags1: set[str] = set()
        for measurement_name_text in measurements_name_text.split(','):
            measurement_name_text = measurement_name_text.strip()
            if measurement_name_text in LEGAL_EXTERNAL_MEASUREMENT_NAME:
                measurements_name.append(measurement_name_text)
            else:
                raise ValueError(f'Invalid measurement name {measurement_name_text}')

        for tag_text in tags0_text.split(','):
            tags0.add(tag_text.strip())
        for tag_text in tags1_text.split(','):
            tags1.add(tag_text.strip())
        must_has_tag = BOOL_STR_MAP[must_has_tag_text.strip()]
        return External_Analysis_Setting(measurements_name, tags0, tags1, must_has_tag)


class Cli:
    def __init__(self):
        pass

    def get_file_path(self) -> Path:
        message = '请将待分析数据csv文件拖入，并按回车确认\n'
        while True:
            text = input(message).strip()
            if text[0] == '"':
                text = text[1: -1]
            path = Path(text)
            if path.is_file():
                return path
            else:
                message = '文件地址错误，请重新尝试拖入待分析数据csv文件，并按回车确认\n'

    def get_file_data(self, path: Path, coding: str) -> list[list[str]]:
        output: list[list[str]] = []
        if coding == 'utf-8':
            coding = 'utf-8-sig'
        with path.open('r', encoding=coding) as f:
            reader = csv.reader(f)
            for row in reader:
                output.append(row)
        return output